from math import pi, sin, cos, asin, acos, atan, atan2, sqrt, copysign
from pyglet.gl import *

from awesum import Terrain, Vector3, Quaternion, degToRad, radToDeg, distSq
from part import TrackPart

class RaceTrackPart(TrackPart):
	HIGH_END = .8
	HIGH_BOUND = .7
	LOW_END = .2
	LOW_BOUND = .3
	
	def yHeight(self, x, z):
		return self.baseHeight
	
	@staticmethod
	def idCorner(tp, orientation):
		"""Generate corner based on radius, and set the track part's info
		based on orientation. Values are:
		1	=	Top left
		2	=	Top right
		3	=	Bottom right
		4	=	Bottom left
		"""
		block = tp.game["BLOCK_SIZE"]
		width = tp.size.x
		baseRadius = block.x * (width - 1)
		stepAmt = block.x / width
		
		if orientation == 1:
			tp.drawPos = Vector3(tp.loc.x * block.x + baseRadius + block.x, tp.baseHeight, tp.loc.z * block.z)
			tp.drawRot = Vector3(0., 180., 0.)
		elif orientation == 2:
			tp.drawPos = Vector3(tp.loc.x * block.x, tp.baseHeight, tp.loc.z * block.z)
			tp.drawRot = Vector3(0., 90., 0.)
		elif orientation == 3:
			tp.drawPos = Vector3(tp.loc.x * block.x, tp.baseHeight, tp.loc.z * block.z - block.x * width)
			tp.drawRot = Vector3(0., 0., 0.)
		elif orientation == 4:
			tp.drawPos = Vector3(tp.loc.x * block.x + baseRadius + block.x, tp.baseHeight, tp.loc.z * block.z - tp.size.x * block.x)
			tp.drawRot = Vector3(0., 270., 0.)
		else:
			print "Orientation out of range for ", tp
		
		name = "race_corner_" + str(width)
		if name not in tp.game.models:
			id = tp.game.models[name]
			
			glNewList(id, GL_COMPILE)
			glBegin(GL_QUADS)
			for r in range(0, 90, stepAmt): #produces BR oriented corner
				r2 = r + stepAmt
				rOutside = baseRadius + RaceTrackPart.HIGH_END * block.x
				rInside = baseRadius + RaceTrackPart.LOW_END * block.x
				glTexCoord2f(0, 0)
				glVertex3f(cos(r * degToRad) * rOutside, 0., sin(r * degToRad) * rOutside)
				glTexCoord2f(1, 0)
				glVertex3f(cos(r * degToRad) * rInside, 0., sin(r * degToRad) * rInside)
				glTexCoord2f(1, 1)
				glVertex3f(cos(r2 * degToRad) * rInside, 0., sin(r2 * degToRad) * rInside)
				glTexCoord2f(0, 1)
				glVertex3f(cos(r2 * degToRad) * rOutside, 0., sin(r2 * degToRad) * rOutside)
			glEnd()
			glEndList()
		else:
			id = tp.game.models[name]
		return id

class TrackVertical(RaceTrackPart):
	def __init__(self, loc, size, coeff=1, eq=0):
		super(TrackVertical, self).__init__(loc, size, coeff, eq)
	
	def load(self, game=None):
		super(TrackVertical, self).load(game)
		
		block = game["BLOCK_SIZE"]
		depth = self.size.z * block.z
		xl = RaceTrackPart.LOW_END * block.x
		xr = RaceTrackPart.HIGH_END * block.x

		glNewList(self.gl.id, GL_COMPILE)
		glTranslatef(self.loc.x * block.x, 0, self.loc.z * block.z)
		glBegin(GL_QUADS)
		for z in range(0, depth - 1, -1):
			z2 = z + 1
			glTexCoord2f(0, 0)
			glVertex3f(xl, self.yHeight(xl, z), z)
			glTexCoord2f(1, 0)
			glVertex3f(xr, self.yHeight(xr, z), z)
			glTexCoord2f(1, 1)
			glVertex3f(xr, self.yHeight(xr, z2), z2)
			glTexCoord2f(0, 1)
			glVertex3f(xl, self.yHeight(xl, z2), z2)
		glEnd()
		glEndList()
	
	def getNextPoint(self, d):
		pt = d.origin.copy()
		block = self.game["BLOCK_SIZE"]
		speed = d.move.speed
		if speed:
			vMove = d.move.quat.rotateV(Vector3(0., 0., d.move.speed))
			vMoveInv = d.move.quat.rotateV(Vector3(0., 0., -d.move.speed))
			newPt = pt + vMove
			invPt = pt + vMoveInv
			xl = RaceTrackPart.LOW_BOUND * block.x
			xr = RaceTrackPart.HIGH_BOUND * block.x
			
			distInX = newPt.x - self.xMin
			distInZ = newPt.z - self.zMax
			
			#Limit the car to stay within the track boundaries
			if distInX < xl: newPt.x = self.xMin + xl
			if distInX > xr: newPt.x = self.xMin + xr
			newPt.y = yCar = self.yHeight(distInX, distInZ)
			
			#Align the car's rotation to the track slope
			aPrev = atan((invPt.y - pt.y) / -vMoveInv.z)# if speed != 0 else 0
			a = atan((newPt.y - pt.y) / vMove.z)# if speed != 0 else 0
			
			q = Quaternion().setEuler(Vector3(-a - aPrev, 0., 0.))
			d.move.quat = q * d.move.quat
			
			d.moved = newPt - pt
			
			pt = newPt
		
		return pt
	
	def yHeight(self, x, z):
		if self.size.y > 1:
			block = self.game["BLOCK_SIZE"]
			sizeY = self.size.y - 1
			sizeZ = self.size.z * block.z
			yCoeff = sizeY * block.y * self.coeff
			if self.eq == 1:	#equation for more convex
				return self.baseHeight + (yCoeff * cos(-z*pi / sizeZ)/2. + .5 * yCoeff) + (sizeY * cos((-z-sizeZ/2)*pi / (sizeZ/2)) + sizeY)
			elif self.eq == 2:	#equation for more concave
				return self.baseHeight + (yCoeff * cos(-z*pi / sizeZ)/2. + .5 * yCoeff) - (sizeY * cos((-z-sizeZ/2)*pi / (sizeZ/2)) + sizeY)
			return self.baseHeight + yCoeff * cos(-z*pi / sizeZ)/2. + .5 * yCoeff
			#y=(-12cos(x*pi/30)/2 + .5*-12) + (2cos((x-15)*pi / 15)+ 2)
		return self.baseHeight
	
	def getExits(self):
		block = self.game["BLOCK_SIZE"]
		e1 = self.loc.copy()
		if self.coeff > 0:
			e1.y += self.size.y - 1
		elif self.coeff < 0:
			e1.y -= self.size.y - 1
		e2 = self.loc + (0, 0, self.size.z - 1)
		e2.y = int(self.yHeight(0, self.size.z * block.z) / block.y)
		return [e1, e2]
	
	@property
	def yMin(self):
		y = self.loc.y
		if self.coeff < 0:
			y -= self.size.y - 1
		return y * self.game["BLOCK_SIZE"].y
			
	@property
	def yMax(self):
		return self.yMin + self.size.y * self.game["BLOCK_SIZE"].y

class TrackHorizontal(RaceTrackPart):
	def __init__(self, loc, size, coeff=1, eq=0):
		super(TrackHorizontal, self).__init__(loc, size, coeff, eq)
	
	def load(self, game=None):
		super(TrackHorizontal, self).load(game)
		
		block = game["BLOCK_SIZE"]
		width = self.size.x * block.x
		zf = RaceTrackPart.HIGH_END * block.z
		zb = RaceTrackPart.LOW_END * block.z

		glNewList(self.gl.id, GL_COMPILE)
		glTranslatef(self.loc.x * block.x, 0, self.loc.z * block.z)
		glBegin(GL_QUADS)
		for x in range(0, width):
			x2 = x + 1
			glTexCoord2f(0, 0)
			glVertex3f(x, self.yHeight(x, zf), zf)
			glTexCoord2f(1, 0)
			glVertex3f(x, self.yHeight(x, zb), zb)
			glTexCoord2f(1, 1)
			glVertex3f(x2, self.yHeight(x2, zb), zb)
			glTexCoord2f(0, 1)
			glVertex3f(x2, self.yHeight(x2, zf), zf)
		glEnd()
		glEndList()
	
	def getNextPoint(self, d):
		pt = d.origin.copy()
		block = self.game["BLOCK_SIZE"]
		speed = d.move.speed
		if speed:
			vMove = d.move.quat.rotateV(Vector3(0., 0., d.move.speed))
			vMoveInv = d.move.quat.rotateV(Vector3(0., 0., -d.move.speed))
			newPt = pt + vMove
			invPt = pt + vMoveInv
			zf = RaceTrackPart.LOW_BOUND * block.z
			zb = RaceTrackPart.HIGH_BOUND * block.z
			
			distInX = newPt.x - self.xMin
			distInZ = self.zMax - newPt.z# - self.zMin
			
			if -distInZ < zb: newPt.z = self.zMax + zb
			if -distInZ > zf: newPt.z = self.zMax + zf
			newPt.y = self.yHeight(distInX, distInZ)
			
			#Align the car's rotation to the track slope
			aPrev = atan((invPt.y - pt.y) / vMoveInv.x)# if speed != 0 else 0
			a = atan((newPt.y - pt.y) / vMove.x)# if speed != 0 else 0
			
			q = Quaternion().setEuler(Vector3(0., 0., -aPrev + a))
			d.move.quat = q * d.move.quat
			
			d.moved = newPt - pt
			
			pt = newPt
		
		return pt
	
	def yHeight(self, x, z):
		if self.size.y > 1:
			block = self.game["BLOCK_SIZE"]
			sizeY = self.size.y - 1
			sizeX = self.size.x * block.x
			yCoeff = sizeY * block.y * self.coeff
			if self.eq == 1:
				return self.baseHeight + (yCoeff * cos(x*pi / sizeX)/2. + .5 * yCoeff) + (sizeY * cos((x-sizeX/2)*pi / (sizeX/2)) + sizeY)
			elif self.eq == 2:
				return self.baseHeight + (yCoeff * cos(x*pi / sizeX)/2. + .5 * yCoeff) - (sizeY * cos((x-sizeX/2)*pi / (sizeX/2)) + sizeY)
			return self.baseHeight + yCoeff * cos(x*pi / sizeX)/2. + .5 * yCoeff
		return self.baseHeight
		
	def getExits(self):
		block = self.game["BLOCK_SIZE"]
		e1 = self.loc.copy()
		if self.coeff > 0:
			e1.y += self.size.y - 1
		elif self.coeff < 0:
			e1.y -= self.size.y - 1
		e2 = self.loc + (self.size.x - 1, 0, 0)
		e2.y = int(self.yHeight(self.size.x * block.x, 0) / block.y)
		return [e1, e2]
	
	@property
	def yMin(self):
		y = self.loc.y
		if self.coeff < 0:
			y -= self.size.y - 1
		return y * self.game["BLOCK_SIZE"].y
			
	@property
	def yMax(self):
		return self.yMin + self.size.y * self.game["BLOCK_SIZE"].y

class TrackTLCurve(RaceTrackPart):
	def __init__(self, loc, size, coeff=1):
		super(TrackTLCurve, self).__init__(loc, size, coeff)
	
	def load(self, game=None):
		#super(TrackTLCurve, self).load(game)
		self.game = game
		self.gl.id = RaceTrackPart.idCorner(self, 1)

	def getNextPoint(self, d):
		pt = d.origin.copy()
		v = d.move.quat.rotateV(Vector3(0., 0., d.move.speed))
		newPt = pt + v
		
		block = self.game["BLOCK_SIZE"]
		distInX = self.xMax - newPt.x
		distInZ = self.zMax - newPt.z
		baseRadius = (self.size.x - 1) * block.x
		carHyp = sqrt(distInX**2 + distInZ**2)
		innerBound = baseRadius + RaceTrackPart.LOW_BOUND * block.x
		outerBound = baseRadius + RaceTrackPart.HIGH_BOUND * block.x
		#Limit the car to stay within the track boundaries
		if carHyp < innerBound:
			ang = atan2(distInZ, distInX)
			newPt.x = self.xMax - cos(ang) * innerBound
			newPt.z = self.zMax - sin(ang) * innerBound
		if carHyp > outerBound:
			ang = atan2(distInZ, distInX)
			newPt.x = self.xMax - cos(ang) * outerBound
			newPt.z = self.zMax - sin(ang) * outerBound
		newPt.y = self.baseHeight #self.yHeight(pt.x - self.xMin, self.zMax - pt1.z)
		
		d.move.rotation.z = 0
		d.move.rotation.x = 0
			
		d.moved = newPt - pt
		
		return newPt
	
	def getExits(self):
		return [self.loc.copy(), self.loc + (self.size.x - 1, 0, self.size.z - 1)]

class TrackTRCurve(RaceTrackPart):
	def __init__(self, loc, size, coeff=1):
		super(TrackTRCurve, self).__init__(loc, size, coeff)
	
	def load(self, game=None):
		#super(TrackTLCurve, self).load(game)
		self.game = game
		self.gl.id = RaceTrackPart.idCorner(self, 2)
	
	def getNextPoint(self, d):
		pt = d.origin.copy()
		v = d.move.quat.rotateV(Vector3(0., 0., d.move.speed))
		newPt = pt + v
		
		block = self.game["BLOCK_SIZE"]
		distInX = newPt.x - self.xMin
		distInZ = self.zMax - newPt.z
		baseRadius = (self.size.x - 1) * block.x
		carHyp = sqrt(distInX**2 + distInZ**2)
		innerBound = baseRadius + RaceTrackPart.LOW_BOUND * block.x
		outerBound = baseRadius + RaceTrackPart.HIGH_BOUND * block.x
		#Limit the car to stay within the track boundaries
		if carHyp < innerBound:
			ang = atan2(distInZ, distInX)
			newPt.x = self.xMin + cos(ang) * innerBound
			newPt.z = self.zMax - sin(ang) * innerBound
		if carHyp > outerBound:
			ang = atan2(distInZ, distInX)
			newPt.x = self.xMin + cos(ang) * outerBound
			newPt.z = self.zMax - sin(ang) * outerBound
		newPt.y = self.baseHeight #self.yHeight(pt.x - self.xMin, self.zMax - pt1.z)
		
		d.move.rotation.z = 0
		d.move.rotation.x = 0
			
		d.moved = newPt - pt
		
		return newPt
	
	def getExits(self):
		return [self.loc + (0, 0, self.size.z - 1), self.loc + (self.size.x - 1, 0, 0)]
		
class TrackBLCurve(RaceTrackPart):
	def __init__(self, loc, size, coeff=1):
		super(TrackBLCurve, self).__init__(loc, size, coeff)
	
	def load(self, game=None):
		#super(TrackTLCurve, self).load(game)
		self.game = game
		self.gl.id = RaceTrackPart.idCorner(self, 4)
	
	def getNextPoint(self, d):
		pt = d.origin.copy()
		v = d.move.quat.rotateV(Vector3(0., 0., d.move.speed))
		newPt = pt + v
		
		block = self.game["BLOCK_SIZE"]
		distInX = self.xMax - newPt.x
		distInZ = newPt.z - self.zMin
		baseRadius = (self.size.x - 1) * block.x
		carHyp = sqrt(distInX**2 + distInZ**2)
		innerBound = baseRadius + RaceTrackPart.LOW_BOUND * block.x
		outerBound = baseRadius + RaceTrackPart.HIGH_BOUND * block.x
		#Limit the car to stay within the track boundaries
		if carHyp < innerBound:
			ang = atan2(distInZ, distInX)
			newPt.x = self.xMax - cos(ang) * innerBound
			newPt.z = self.zMin + sin(ang) * innerBound
		if carHyp > outerBound:
			ang = atan2(distInZ, distInX)
			newPt.x = self.xMax - cos(ang) * outerBound
			newPt.z = self.zMin + sin(ang) * outerBound
		newPt.y = self.baseHeight #self.yHeight(pt.x - self.xMin, self.zMax - pt1.z)
		
		d.move.rotation.z = 0
		d.move.rotation.x = 0
			
		d.moved = newPt - pt
		
		return newPt
	
	def getExits(self):
		return [self.loc + (0, 0, self.size.z - 1), self.loc + (self.size.x - 1, 0, 0)]

class TrackBRCurve(RaceTrackPart):
	def __init__(self, loc, size, coeff=1):
		super(TrackBRCurve, self).__init__(loc, size, coeff)
	
	def load(self, game=None):
		#super(TrackTLCurve, self).load(game)
		self.game = game
		self.gl.id = RaceTrackPart.idCorner(self, 3)
	
	def getNextPoint(self, d):
		pt = d.origin.copy()
		v = d.move.quat.rotateV(Vector3(0., 0., d.move.speed))
		newPt = pt + v
		
		block = self.game["BLOCK_SIZE"]
		distInX = newPt.x - self.xMin
		distInZ = newPt.z - self.zMin
		baseRadius = (self.size.x - 1) * block.x
		carHyp = sqrt(distInX**2 + distInZ**2)
		innerBound = baseRadius + RaceTrackPart.LOW_BOUND * block.x
		outerBound = baseRadius + RaceTrackPart.HIGH_BOUND * block.x
		#Limit the car to stay within the track boundaries
		if carHyp < innerBound:
			ang = atan2(distInZ, distInX)
			newPt.x = self.xMin + cos(ang) * innerBound
			newPt.z = self.zMin + sin(ang) * innerBound
		if carHyp > outerBound:
			ang = atan2(distInZ, distInX)
			newPt.x = self.xMin + cos(ang) * outerBound
			newPt.z = self.zMin + sin(ang) * outerBound
		newPt.y = self.baseHeight #self.yHeight(pt.x - self.xMin, self.zMax - pt1.z)
		
		d.move.rotation.z = 0
		d.move.rotation.x = 0
			
		d.moved = newPt - pt
		
		return newPt
	
	def getExits(self):
		return [self.loc.copy(), self.loc + (self.size.x - 1, 0, self.size.z - 1)]
